using System;
using System.Collections;
using Velocity4Net.Errors;

namespace Velocity4Net.Runtime.Logging
{
    /**
     * <p>
     * This class is responsible for instantiating the correct LogChute
     * </p>
     *
     * <p>
     * The approach is :
     * </p>
     * <ul>
     * <li>
     *      First try to see if the user is passing in a living object
     *      that is a LogChute, allowing the app to give its living
     *      custom loggers.
     *  </li>
     *  <li>
     *       Next, run through the (possible) list of classes specified
     *       specified as loggers, taking the first one that appears to
     *       work.  This is how we support finding logkit, log4j or
     *       jdk logging, whichever is in the classpath and found first,
     *       as all three are listed as defaults.
     *  </li>
     *  <li>
     *      Finally, we turn to the System.err stream and print log messages
     *      to it if nothing else works.
     *  </li>
     *
     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
     * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
     * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
     * @author <a href="mailto:nbubna@apache.org">Nathan Bubna</a>
     * @version $Id: LogManager.java 991708 2010-09-01 21:17:56Z nbubna $
     */
    public class LogManager
    {
        // Creates a new logging system or returns an existing one
        // specified by the application.
        private static LogChute CreateLogChute(RuntimeServices rsvc)
        {
            Log log = rsvc.getLog();

            /* If a LogChute or LogSystem instance was set as a configuration
             * value, use that.  This is any class the user specifies.
             */
            Object o = rsvc.getProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM);
            if (o != null)
            {
                // first check for a LogChute
                if (o is LogChute)
                {
                    try
                    {
                        ((LogChute)o).init(rsvc);
                        return (LogChute)o;
                    }
                    catch (System.Exception e)
                    {
                        String msg = "Could not init runtime.Logsystem " + o;
                        log.error(msg, e);
                        throw new VelocityException(msg, e);
                    }
                }
                else
                {
                    String msg = o.GetType().Name + " object set as runtime.Logsystem is not a valid log implementation.";
                    log.error(msg);
                    throw new VelocityException(msg);
                }
            }

            /* otherwise, see if a class was specified.  You can put multiple
             * classes, and we use the first one we find.
             *
             * Note that the default value of this property contains the
             * AvalonLogChute, the Log4JLogChute, CommonsLogLogChute,
             * ServletLogChute, and the JdkLogChute for
             * convenience - so we use whichever we works first.
             */
            IList classes = new ArrayList();
            Object obj = rsvc.getProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS);

            /*
             *  we might have a list, or not - so check
             */
            if (obj is IList)
            {
                classes = (IList)obj;
            }
            else if (obj is String)
            {
                classes.Add(obj);
            }

            /*
             *  now run through the list, trying each.  It's ok to
             *  fail with a class not found, as we do this to also
             *  search out a default simple file logger
             */
            foreach (String claz in classes)
            {
                if (claz != null && claz.Length > 0)
                {
                    log.debug("Trying to use logger class " + claz);
                    try
                    {
                        Type t = Type.GetType(claz);
                        o = t.Assembly.CreateInstance(t.FullName);
                        if (o is LogChute)
                        {
                            ((LogChute)o).init(rsvc);
                            log.debug("Using logger class " + claz);
                            return (LogChute)o;
                        }

                        else
                        {
                            String msg = "The specified logger class " + claz +
                                         " does not implement the " + typeof(LogChute).Name + " interface.";
                            log.error(msg);
                            // be extra informative if it appears to be a classloader issue
                            // this should match all our provided LogChutes
                            if (isProbablyProvidedLogChute(claz))
                            {
                                // if it's likely to be ours, tip them off about classloader stuff
                                log.error("This appears to be a ClassLoader issue.  Check for multiple Velocity jars in your classpath.");
                            }
                            throw new VelocityException(msg);
                        }
                    }
                    catch (TypeLoadException ncdfe)
                    {
                        // note these errors for anyone debugging the app
                        if (isProbablyProvidedLogChute(claz))
                        {
                            log.debug("Target log system for " + claz +
                                      " is not available (" + ncdfe.ToString() +
                                      ").  Falling back to next log system...");
                        }
                        else
                        {
                            log.debug("Couldn't find class " + claz +
                                      " or necessary supporting classes in classpath.",
                                      ncdfe);
                        }
                    }
                    catch (System.Reflection.TargetInvocationException uoe)
                    {
                        // note these errors for anyone debugging the app
                        if (isProbablyProvidedLogChute(claz))
                        {
                            log.debug("Target log system for " + claz +
                                      " is not supported (" + uoe.ToString() +
                                      ").  Falling back to next log system...");
                        }
                        else
                        {
                            log.debug("Couldn't find necessary resources for " + claz, uoe);
                        }
                    }
                    catch (System.Exception e)
                    {
                        String msg = "Failed to initialize an instance of " + claz +
                                     " with the current runtime configuration.";
                        // log unexpected init exception at higher priority
                        log.error(msg, e);
                        throw new VelocityException(msg, e);
                    }
                }
            }

            /* If the above failed, that means either the user specified a
             * logging class that we can't find, there weren't the necessary
             * dependencies in the classpath for it, or there were the same
             * problems for the default loggers, log4j and Java1.4+.
             * Since we really don't know and we want to be sure the user knows
             * that something went wrong with the logging, let's fall back to the
             * surefire SystemLogChute. No panicking or failing to log!!
             */
            LogChute slc = new SystemLogChute();
            slc.init(rsvc);
            log.debug("Using SystemLogChute.");
            return slc;
        }

        /**
         * Simply tells whether the specified classname probably is provided
         * by Velocity or is implemented by someone else.  Not surefire, but
         * it'll probably always be right.  In any case, this method shouldn't
         * be relied upon for anything important.
         */
        private static bool isProbablyProvidedLogChute(String claz)
        {
            if (claz == null)
            {
                return false;
            }
            else
            {
                return (claz.StartsWith("org.apache.velocity.runtime.log")
                        && claz.EndsWith("LogChute"));
            }
        }

        /**
         * Update the Log instance with the appropriate LogChute and other
         * settings determined by the RuntimeServices.
         * @param log
         * @param rsvc
         * @throws Exception
         * @since 1.5
         */
        public static void UpdateLog(Log log, RuntimeServices rsvc)
        {
            // create a new LogChute using the RuntimeServices
            LogChute newLogChute = CreateLogChute(rsvc);
            LogChute oldLogChute = log.LogChute;

            // pass the new LogChute to the log first,
            // (if the old was a HoldingLogChute, we don't want it
            //  to accrue new messages during the transfer below)
            log.setLogChute(newLogChute);

            // If the old LogChute was the pre-Init logger,
            // dump its messages into the new system.
            if (oldLogChute is HoldingLogChute)
            {
                HoldingLogChute hlc = (HoldingLogChute)oldLogChute;
                hlc.transferTo(newLogChute);
            }
        }

    }

}
